from PyQt5 import QtWidgets, QtCore, QtGui
import pandas as pd

from gui.attribute_table_dialog import AttributeTable
from gui.create_layer_dialog import CreateLayerDialog
from gui.database_connection_dialog import DatabaseConnectionWindow
from gui.style_dialog import PointStyleDialog, LineStyleDialog, PolygonStyleDialog
from gui.paint_area import PaintArea, ToolStatus
from gui.import_layer_dialog import ImportLayerToPostgisDialog
from geometry.spatial_statistics import StandardDeviationalEllipseDialog
from geometry.layer import Layer
from geometry.geometry_converter import GeometryConverter


class MainWindow(QtWidgets.QMainWindow):
    """
    主窗体
    """
    layerSignal = QtCore.pyqtSignal(Layer)  # 图层信号
    toolStatusSignal = QtCore.pyqtSignal(int)  # 工具信号

    def __init__(self):
        """构造函数
        :param self: 相当于c++和java的this
        :return: nothing
        """

        super().__init__()  # 调用父类的构造函数，MainWindow（子类）的父类是？？（涉及Qt里类的继承吗？）
        self.conn = None  # 初始化变量conn（数据库连接）为空

        # 初始化UI、创建行为、创建菜单栏
        self.initUI()
        self.createMenus()
        self.createStatusBar()

    def showCoordinate(self, coord):
        """用messageLabel的setText方法来显示当前的x和y坐标

        :param self: it is self
        :return: nothing
        """
        self.coordLabel.setText("x:{0:.3f}, y:{1:.3f}".format(*coord))

    def initUI(self):
        """初始化UI界面，使用了PaintArea类

        :param self: it is self
        :return: nothing
        """
        # 设置窗体位置、大小、标题
        self.setGeometry(400, 200, 1000, 600)
        self.setMinimumSize(600, 400)
        self.setWindowTitle('MINIGIS')

        # 布局
        self.centralwidget = QtWidgets.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setContentsMargins(10, 10, 10, 0)

        # TreeWidget 数据库连接管理
        self.dbTree = DBConnectionTree(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHeightForWidth(self.dbTree.sizePolicy().hasHeightForWidth())
        self.dbTree.setSizePolicy(sizePolicy)
        self.dbTree.headerItem().setText(0, "数据库连接")
        self.gridLayout.addWidget(self.dbTree, 0, 0, 1, 1)

        # ListView 图层管理
        self.TOCLabel = QtWidgets.QLabel("图层")
        self.TOCLabel.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum))
        self.gridLayout.addWidget(self.TOCLabel, 1, 0, 1, 1)
        self.tableOfContents = QtWidgets.QListWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHeightForWidth(self.tableOfContents.sizePolicy().hasHeightForWidth())
        self.tableOfContents.setSizePolicy(sizePolicy)
        self.tableOfContents.currentItemChanged.connect(self.emitCurrentLayer)
        self.gridLayout.addWidget(self.tableOfContents, 2, 0, 1, 1)

        # paintArea 画图区域
        self.paintArea = PaintArea(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHeightForWidth(self.paintArea.sizePolicy().hasHeightForWidth())
        self.layerSignal.connect(self.paintArea.slotGetCurrentLayer)
        self.toolStatusSignal.connect(self.paintArea.slotGetToolStatus)
        self.paintArea.setSizePolicy(sizePolicy)
        self.gridLayout.addWidget(self.paintArea, 0, 1, 3, 1)

    def emitCurrentLayer(self):
        currentItem = self.sender().itemWidget(self.sender().currentItem())
        if currentItem is not None:
            currentLayer = currentItem.layer
            self.layerSignal.emit(currentLayer)

    def createMenus(self):
        """创建菜单栏

        :param self: it is self
        :return: nothing
        """
        # 数据库
        self.dbConnectAct = QtWidgets.QAction("数据库", self)
        self.dbConnectAct.triggered.connect(self.addDBConnectWindow)
        self.menuBar().addAction(self.dbConnectAct)
        # 加载数据
        self.fileMenu = self.menuBar().addMenu("加载数据")
        self.loadDataFromFileAct = QtWidgets.QAction("导入shp文件", self)
        self.loadDataFromFileAct.triggered.connect(self.loadDataFromFile)
        self.fileMenu.addAction(self.loadDataFromFileAct)
        self.loadPointFromCsvAct = QtWidgets.QAction("导入csv文件", self)
        self.loadPointFromCsvAct.triggered.connect(self.loadPointFromCsv)
        self.fileMenu.addAction(self.loadPointFromCsvAct)
        # 选择
        self.selectionMenu = self.menuBar().addMenu("选择")
        self.selectByPointAct = QtWidgets.QAction("点选", self)
        self.selectByPointAct.triggered.connect(self.setToolStatus)
        self.selectionMenu.addAction(self.selectByPointAct)
        self.selectByRectAct = QtWidgets.QAction("框选", self)
        self.selectByRectAct.triggered.connect(self.setToolStatus)
        self.selectionMenu.addAction(self.selectByRectAct)
        self.deselectAllAct = QtWidgets.QAction("清空选区", self)
        self.deselectAllAct.triggered.connect(self.paintArea.deselectAll)
        self.selectionMenu.addAction(self.deselectAllAct)
        # 创建图层
        self.createTemporaryLayer = QtWidgets.QAction("创建图层", self)
        self.createTemporaryLayer.triggered.connect(self.createTemporaryLayerWindow)
        self.menuBar().addAction(self.createTemporaryLayer)
        # 交互输入
        self.editGeometryMenu = self.menuBar().addMenu("交互输入")
        self.editGeometryMenu.setEnabled(False)
        self.editGeometryAct = QtWidgets.QAction("交互输入", self)
        self.editGeometryAct.setCheckable(True)
        self.editGeometryAct.triggered.connect(self.setToolStatus)
        self.editGeometryMenu.addAction(self.editGeometryAct)
        # 空间统计
        self.spatialStatisticsMenu = self.menuBar().addMenu("空间统计")
        self.standardEllipseAct = QtWidgets.QAction("标准差椭圆", self)
        self.spatialStatisticsMenu.addAction(self.standardEllipseAct)
        self.standardEllipseAct.triggered.connect(self.standardEllipse)

    def standardEllipse(self):
        self.standardEllipseDialog = StandardDeviationalEllipseDialog(self.paintArea.layer_list)
        self.standardEllipseDialog.show()
        self.standardEllipseDialog.layerSignal.connect(self.loadSpatialStatisticsResult)

    def loadSpatialStatisticsResult(self, layer):
        self.addItemToTOC(layer)
        self.paintArea.addLayer(layer)
        self.paintArea.update()

    def createTemporaryLayerWindow(self):
        self.createLayerDialog = CreateLayerDialog()
        self.createLayerDialog.show()
        self.createLayerDialog.layerSignal.connect(self.addTempLayer)

    def addTempLayer(self, layer):
        self.addItemToTOC(layer)
        self.paintArea.addLayer(layer)
        layer.setBBox(*self.paintArea.getViewBoundingBox())
        self.paintArea.setViewBoundingBox(*layer.getBBox())
        self.paintArea.update()

    def createStatusBar(self):
        self.statusbar = QtWidgets.QStatusBar(self)
        self.setStatusBar(self.statusbar)
        self.statusbar.setMaximumHeight(30)

        # 坐标显示
        self.coordLabel = QtWidgets.QLabel("x: , y: ", self.statusbar)
        self.coordLabel.setFixedWidth(200)
        self.paintArea.coordinateSignal.connect(self.showCoordinate)
        self.statusbar.addPermanentWidget(self.coordLabel)
        self.statusbar.setContentsMargins(2, 2, 2, 2)

    def setToolStatus(self):
        if self.sender().text() == "点选":
            self.toolStatusSignal.emit(ToolStatus.selectByPoint.value)
        elif self.sender().text() == "框选":
            self.toolStatusSignal.emit(ToolStatus.selectByRect.value)
        elif self.sender().text() == "交互输入":
            if self.sender().isChecked():  # 开启输入状态
                self.toolStatusSignal.emit(ToolStatus.draw.value)
            else:  # 关闭输入状态
                self.toolStatusSignal.emit(ToolStatus.normal.value)

    def loadDataFromFile(self):
        fileName = QtWidgets.QFileDialog().getOpenFileName(filter="shapefile(*.shp)")
        if fileName[0] != '':
            layer = GeometryConverter().shpToLayer(fileName[0])
            self.addItemToTOC(layer)
            self.paintArea.addLayer(layer)
            self.paintArea.setViewBoundingBox(*layer.getBBox())
            self.paintArea.update()

    def loadPointFromCsv(self):
        fileName = QtWidgets.QFileDialog().getOpenFileName(filter="csv(*.csv)")
        if fileName[0] != '':
            layer = GeometryConverter().csvToLayer(fileName[0])
            self.addItemToTOC(layer)
            self.paintArea.addLayer(layer)
            self.paintArea.setViewBoundingBox(*layer.getBBox())
            self.paintArea.update()

    def addDBConnectWindow(self):
        """创建数据库连接窗口 来自database_connection.py

        :param self: it is self
        :return: nothing
        """
        self.dbConnectionWindow = DatabaseConnectionWindow()
        self.dbConnectionWindow.show()
        self.dbConnectionWindow.connSignal.connect(self.addDBConnInfo)

    def addDBConnInfo(self, connInfo):
        # 添加到TreeWidget中
        connItem = DBConnectionItem(self.dbTree)
        connItem.setText(0, connInfo.database)
        connItem.setConnInfo(connInfo)

    def loadDataFromDB(self, tableName, conn):
        """调用我们写好的函数：读数据、设置画图区域、在画布上增加图层并输入数据，最后更新qt窗口

        :param self: it is self
        :param tableName: 数据库postgis_31_sample里的数据表的名字，如test2
        :return: nothing
        """
        if conn is None or conn.closed:
            self.msgBox = QtWidgets.QMessageBox(text=f"请重新连接数据库")
            self.msgBox.show()
            return

        converter = GeometryConverter()
        converter.setDBConnection(conn)
        layer = converter.postgisToLayer(tableName)
        bbox = layer.getBBox()
        self.paintArea.setViewBoundingBox(*bbox)
        self.paintArea.addLayer(layer)
        self.addItemToTOC(layer)
        self.paintArea.update()

    def addItemToTOC(self, layer):
        listItem = QtWidgets.QListWidgetItem()
        layerItem = LayerItem(self)
        layerItem.setCheckState(QtCore.Qt.CheckState.Checked)   ##！！！！此处不要注释
        layerItem.setLayer(layer)

        self.tableOfContents.insertItem(0, listItem)
        self.tableOfContents.setItemWidget(listItem, layerItem)  # 在这个条目中放置layerItem
        # 把新加入的项设置为当前项
        self.tableOfContents.setCurrentRow(0)

        # 把所有checkBox的信号都引向同一个槽
        layerItem.stateChanged.connect(self.slotLayerStateChanged)
        layerItem.layerSignal.connect(self.slotRemoveLayer)

    def slotLayerStateChanged(self):
        layerItem = self.sender()
        layerItem.layer.visible = not layerItem.layer.visible  # 改变visible状态
        self.paintArea.update()

    def slotRemoveLayer(self, layer):
        self.tableOfContents.takeItem(self.tableOfContents.currentRow())
        self.paintArea.removeLayer(layer)
        if layer.editable:
            self.editGeometryMenu.setEnabled(False)
            self.editGeometryAct.setChecked(False)
            self.paintArea.toolStatus = ToolStatus.normal.value


class DBConnectionTree(QtWidgets.QTreeWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.contextMenu = QtWidgets.QMenu(self)
        self.importLayerAct = QtWidgets.QAction("删除图层")
        self.importLayerAct.triggered.connect(self.deleteLayer)
        self.contextMenu.addAction(self.importLayerAct)
        # 双击
        self.itemDoubleClicked.connect(self.slotDBTreeDoubleClick)

    def contextMenuEvent(self, e):
        if not isinstance(self.currentItem(), DBConnectionItem) and isinstance(self.currentItem(), QtWidgets.QTreeWidgetItem):  # 点击的是图层
            self.contextMenu.exec_(QtGui.QCursor.pos())

    def deleteLayer(self):
        tableName = self.currentItem().text(0)
        sql = f"drop table {tableName}"
        connItem = self.currentItem().parent()
        connItem.doConnect()
        with connItem.conn as conn:
            with conn.cursor() as cursor:
                cursor.execute(sql)
        self.refreshTableList()

    def slotDBTreeDoubleClick(self):
        currentItem = self.currentItem()
        if isinstance(currentItem, DBConnectionItem):  # 点击的是数据库连接信息。进行数据库连接，并获取所有含有geometry的表名
            currentItem.doConnect()  # 连接数据库
            self.refreshTableList()
        elif isinstance(currentItem, QtWidgets.QTreeWidgetItem):  # 点击的是表名。加载该表。
            tableName = currentItem.text(0)
            self.parent().parent().loadDataFromDB(tableName, currentItem.parent().conn)

    def refreshTableList(self):
        if isinstance(self.currentItem(), DBConnectionItem):  # 点击的是数据库连接
            currentConnItem = self.currentItem()
        elif isinstance(self.currentItem(), QtWidgets.QTreeWidgetItem):  # 点击的是表名
            currentConnItem = self.currentItem().parent()
        else:
            return
        if currentConnItem.conn is None:
            return
        # 清空子节点
        for i in range(currentConnItem.childCount()):
            currentConnItem.removeChild(currentConnItem.child(0))
        df = pd.read_sql("""
                        select c.relname
                        from pg_attribute a, pg_class c
                        where c.oid = a.attrelid
                        and format_type (A .atttypid, A .atttypmod) like 'geometry%' --a.attname = 'geom' --可以模糊查询 '%column_name%'
                        and attinhcount in (0)
                        and c.relkind in ('r')  -- r =普通表，i = 索引，S =序列，v = 视图， c =复合类型，s = 特殊，t =TOAST表
                        order by c.relname
                        """, currentConnItem.conn)
        for idx in df.index:
            child = QtWidgets.QTreeWidgetItem(currentConnItem)
            child.setText(0, df.loc[idx, "relname"])


class DBConnectionItem(QtWidgets.QTreeWidgetItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.connInfo = None
        self.conn = None

    def setConnInfo(self, connInfo):
        self.connInfo = connInfo

    def getConnInfo(self):
        return self.connInfo

    def doConnect(self):
        if self.conn is None or self.conn.closed:  # 没有连接或连接已关闭
            try:
                self.conn = self.connInfo.connect()
            except Exception as e:
                self.msgBox = QtWidgets.QMessageBox(text=f"连接失败：{str(e)}")
                self.msgBox.show()


class LayerItem(QtWidgets.QCheckBox):
    layerSignal = QtCore.pyqtSignal(Layer)

    def __init__(self, mainWindow):
        super().__init__()
        self.layer = None
        self.defaultFont = self.font()  # 默认字体
        self.mainWindow = mainWindow
        # 上下文菜单
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)
        self.contextMenu = QtWidgets.QMenu(self)  # 右键菜单

        self.removeLayerAct = QtWidgets.QAction("移除图层")
        self.removeLayerAct.triggered.connect(self.removeLayer)
        self.contextMenu.addAction(self.removeLayerAct)

        self.zoomToLayerAct = QtWidgets.QAction("缩放至图层")
        self.zoomToLayerAct.triggered.connect(self.zoomToLayer)
        self.contextMenu.addAction(self.zoomToLayerAct)

        self.openAttributeTableAct = QtWidgets.QAction("打开属性表")
        self.openAttributeTableAct.triggered.connect(self.openAttributeTable)
        self.contextMenu.addAction(self.openAttributeTableAct)

        self.setStyleAct = QtWidgets.QAction("设置图层样式")
        self.setStyleAct.triggered.connect(self.setLayerStyle)
        self.contextMenu.addAction(self.setStyleAct)

        self.contextMenu.addSeparator()

        self.exportAsShpAct = QtWidgets.QAction("导出为shp")
        self.exportAsShpAct.triggered.connect(self.exportAsShp)
        self.contextMenu.addAction(self.exportAsShpAct)

        self.importToPostgisAct = QtWidgets.QAction("导入数据库")
        self.importToPostgisAct.triggered.connect(self.importToPostgis)
        self.contextMenu.addAction(self.importToPostgisAct)

        self.contextMenu.addSeparator()

        self.turnOnEditModeAct = QtWidgets.QAction("开始编辑")
        self.turnOnEditModeAct.triggered.connect(self.turnOnEditMode)
        self.contextMenu.addAction(self.turnOnEditModeAct)

        self.turnOffEditModeAct = QtWidgets.QAction("停止编辑")
        self.turnOffEditModeAct.triggered.connect(self.turnOffEditMode)
        self.turnOffEditModeAct.setEnabled(False)
        self.contextMenu.addAction(self.turnOffEditModeAct)

    def zoomToLayer(self):
        self.mainWindow.paintArea.setViewBoundingBox(*self.layer.getBBox())
        self.mainWindow.paintArea.update()

    def turnOffEditMode(self):
        """
        关闭编辑模式

        :return:
        """
        self.setStyleSheet("")
        self.setFont(self.defaultFont)
        self.layer.editable = False
        self.turnOffEditModeAct.setEnabled(False)
        self.turnOnEditModeAct.setEnabled(True)
        self.mainWindow.editGeometryAct.setChecked(False)
        self.mainWindow.editGeometryMenu.setEnabled(False)

    def turnOnEditMode(self):
        """
        打开编辑模式（目前只能绘制新图形，不能编辑原有图形）

        :return:
        """
        self.setStyleSheet("QCheckBox {background-color: rgb(0, 255, 255);}")
        self.layer.editable = True
        self.turnOnEditModeAct.setEnabled(False)
        self.turnOffEditModeAct.setEnabled(True)
        self.mainWindow.editGeometryMenu.setEnabled(True)
        self.mainWindow.paintArea.toolStatus = ToolStatus.normal

    def setLayerStyle(self):
        if self.layer.geometryType == "Point":
            self.styleDialog = PointStyleDialog(self.layer)
        elif self.layer.geometryType == "LineString":
            self.styleDialog = LineStyleDialog(self.layer)
        elif self.layer.geometryType == "Polygon":
            self.styleDialog = PolygonStyleDialog(self.layer)

        self.styleDialog.refreshSignal.connect(self.refreshPaintArea)
        self.styleDialog.show()

    def refreshPaintArea(self):
        self.mainWindow.paintArea.update()

    def setLayer(self, layer):
        self.layer = layer
        self.setText(layer.name)

    def showContextMenu(self):
        self.contextMenu.exec_(QtGui.QCursor.pos())

    def removeLayer(self):
        self.layerSignal.emit(self.layer)

    def exportAsShp(self):
        fileName = QtWidgets.QFileDialog().getSaveFileName()
        if fileName[0] != '':
            GeometryConverter().layerToShp(self.layer, fileName[0])

    def importToPostgis(self):
        # 获取所有数据库连接
        connList = []
        mainWindow = self.parent().parent().parent().parent()  # 指向主窗体
        dbTree = mainWindow.dbTree
        for i in range(dbTree.topLevelItemCount()):
            connList.append(dbTree.topLevelItem(i).connInfo)
        self.importToPostgisDialog = ImportLayerToPostgisDialog(self.layer, connList)
        self.importToPostgisDialog.status.connect(mainWindow.dbTree.refreshTableList)  # 刷新图层列表
        self.importToPostgisDialog.show()

    def openAttributeTable(self):
        self.attributeTableWindow = AttributeTable(self.layer, self.mainWindow)
        self.mainWindow.paintArea.refreshSelectSignal.connect(self.attributeTableWindow.showSelected)
        self.attributeTableWindow.show()
